//
// GPSMapEdit
// (c) Konstantin Galichsky (kg@geopainting.com), 2002-2007
//
// Polish format file saver.
//

# include "StdAfx.h"
# include "Polish.h"
# include "Map.h"
# include "GarminTypes.h"
# include "FileWriter.h"
# include "Types.h"
# include "Save.h"
# include "Log.h"
# include "Status.h"
# include "Version.h"
# include "fast_floor.h"
# include "Globals.h"

bool g_bPolish074 = true;

class CTxtSaver : public CMapSaver {
  public:
	virtual bool CanSave (const map_t & _map) {return true;}
	virtual bool Save (HANDLE _hFile, const char * _strFileName, const map_t & _map);
};

DECLARE_DEFAULT_MAP_SAVER (CTxtSaver, "polish", "mp;txt", "Polish format (*.mp;*.txt)");

bool CTxtSaver::Save (HANDLE _hFile, const char * _strFileName, const map_t & _map) {
	SaveAsPolishFormat (_hFile, _strFileName, _map, 0, 100);
	return true;
}

static
void _Write2Dec (CFileWriter & _fw, size_t _c) {
	if (_c <= 9)
		_fw.WriteC ('0' + _c);
	else {
		_fw.WriteC ('0' + (_c / 10));
		_fw.WriteC ('0' + (_c % 10));
	}
}

static
void _WriteHouseNumbers (CFileWriter & _fw, const rgn_t & _rgn, size_t _cPoint, const numbers_t & _Numbers, const numbers_t & _NumbersNext) {
	static const char c_strParity [4] = {'N', 'E', 'O', 'B'};

	_fw.WriteU (_cPoint);
	_fw.WriteC (',');

	_fw.WriteC (c_strParity [_Numbers.wLeftParity < 4 ? _Numbers.wLeftParity : 0]);
	_fw.WriteC (',');
	if (_Numbers.wLeftAfter != 0 && _Numbers.wLeftParity != 0)
		_fw.WriteU (_Numbers.wLeftAfter);
	else
		_fw.WriteS ("-1");
	_fw.WriteC (',');
	if (_NumbersNext.wLeftBefore != 0 && _Numbers.wLeftParity != 0)
		_fw.WriteU (_NumbersNext.wLeftBefore);
	else
		_fw.WriteS ("-1");
	_fw.WriteC (',');

	_fw.WriteC (c_strParity [_Numbers.wRightParity < 4 ? _Numbers.wRightParity : 0]);
	_fw.WriteC (',');
	if (_Numbers.wRightAfter != 0 && _Numbers.wRightParity != 0)
		_fw.WriteU (_Numbers.wRightAfter);
	else
		_fw.WriteS ("-1");
	_fw.WriteC (',');
	if (_NumbersNext.wRightBefore != 0 && _Numbers.wRightParity != 0)
		_fw.WriteU (_NumbersNext.wRightBefore);
	else
		_fw.WriteS ("-1");

	const bool bNoZipLeft   = _Numbers.pZipLeftAfter   == NULL || _Numbers.pZipLeftAfter  ->IsDeleted ();// || (_rgn.pExtra && _Numbers.pZipLeftAfter   == _rgn.pExtra->pZip);
	const bool bNoZipRight  = _Numbers.pZipRightAfter  == NULL || _Numbers.pZipRightAfter ->IsDeleted ();// || (_rgn.pExtra && _Numbers.pZipRightAfter  == _rgn.pExtra->pZip);
	const bool bNoCityLeft  = _Numbers.pCityLeftAfter  == NULL || _Numbers.pCityLeftAfter ->IsDeleted ();// || (_rgn.pExtra && _Numbers.pCityLeftAfter  == _rgn.pExtra->pCity);
	const bool bNoCityRight = _Numbers.pCityRightAfter == NULL || _Numbers.pCityRightAfter->IsDeleted ();// || (_rgn.pExtra && _Numbers.pCityRightAfter == _rgn.pExtra->pCity);
	if (bNoZipLeft && bNoZipRight && bNoCityLeft && bNoCityRight)
		return;
	_fw.WriteC (',');

	_fw.WriteS (bNoZipLeft  ? "-1" : _Numbers.pZipLeftAfter ->strName.c_str ());
	_fw.WriteC (',');
	_fw.WriteS (bNoZipRight ? "-1" : _Numbers.pZipRightAfter->strName.c_str ());
	_fw.WriteC (',');

	if (bNoCityLeft)
		_fw.WriteS ("-1,");
	else {
		_fw.WriteS (_Numbers.pCityLeftAfter->strName.c_str ());
		_fw.WriteC (',');
		_fw.WriteS (_Numbers.pCityLeftAfter->pRegion->strName.c_str ());
		_fw.WriteC (',');
		_fw.WriteS (_Numbers.pCityLeftAfter->pRegion->pCountry->strName.c_str ());
		_fw.WriteC (',');
	}

	if (bNoCityRight)
		_fw.WriteS ("-1");
	else {
		_fw.WriteS (_Numbers.pCityRightAfter->strName.c_str ());
		_fw.WriteC (',');
		_fw.WriteS (_Numbers.pCityRightAfter->pRegion->strName.c_str ());
		_fw.WriteC (',');
		_fw.WriteS (_Numbers.pCityRightAfter->pRegion->pCountry->strName.c_str ());
	}
}

void SaveAsPolishFormat (
	HANDLE _hFile, const char * _strFileName, const map_t & _map,
	size_t _cProgressFrom, size_t _cProgressRange
) {
	// NOTE: the saver really needs to update some fields in the _map.
	map_t & map = const_cast<map_t &> (_map);

	map.CountRGNodes ();
	map.MakeSureAllNodeIDsAreUnique ();
	map.MakeSureAllRoadIDsAreUnique ();


	CFileWriter fw (_hFile);

	size_t cOrdinal = 0;

	// TODO: delete original file_ref.
	map.file_refs.push_back (file_ref_t ());
	file_ref_t & file_ref = map.file_refs.back ();
	file_ref.strFileName = _strFileName;
	file_ref.bAttachment = false;

	// Set precision.
	size_t cPrecOrder = __min (_map.cPrecOrder, 6);
	if (! _map.Levels.empty ()) {
		const size_t cMaxBits = _map.Levels [0].btBits;
		if (cMaxBits >= 25 && cPrecOrder < 6)
			cPrecOrder = 6;
		if (cMaxBits <= 24 && cPrecOrder > 5)
			cPrecOrder = 5;
		if (cMaxBits == 24 && cPrecOrder < 5)
			cPrecOrder = 5;
	}
	fw.SetFPrecision (cPrecOrder);

	SetProgressStatus (_cProgressFrom);

	//
	// Save header.
	//

	fw.WriteS (
		"; Generated by GPSMapEdit " GME_VER_S "\r\n"
		"\r\n"
		"[IMG ID]\r\n"
	);

	if (! _map.bNoHeader) {
		// Save CodePage.
		if (_map.Header.wCodePage != 0) {
			fw.WriteS ("CodePage=");
			fw.WriteU (_map.Header.wCodePage);
			fw.WriteS ("\r\n");
		}
		if (_map.Header.CodeSchema != bpsAscii7) {
			if (_map.Header.CodeSchema == bpsSBCS)
				fw.WriteS ("LblCoding=9\r\n");
			else {
				assert (_map.Header.CodeSchema == bpsMBCS);
				fw.WriteS ("LblCoding=10\r\n");
			}
		}

		// Save ID.
		fw.WriteS ("ID=");
		fw.WriteS (_map.Header.strID.c_str ());
		fw.WriteS ("\r\n");

		// Save Name.
		fw.WriteS ("Name=");
		fw.WriteS (_map.Header.strName.c_str ());
		fw.WriteS ("\r\n");

		// Save TypeSet.
		if (_map.pTypeSet != & g_tsGarmin) {
			fw.WriteS ("TypeSet=");
			fw.WriteS (_map.pTypeSet->strID);
			fw.WriteS ("\r\n");
		}

		// Save Datum.
//		fw.WriteS ("Datum=W84\r\n");

		// Save Elevation.
		switch (_map.Header.ElevationUnits) {
			case unFeet:   break;
			case unMeters: fw.WriteS ("Elevation=M\r\n"); break;
			default: assert (false);
		}

		// Save Preprocess.
		switch (_map.Header.Preprocess) {
			case map_header_t::ppNo:             fw.WriteS ("Preprocess=N\r\n"); break;
			case map_header_t::ppGeneralization: fw.WriteS ("Preprocess=G\r\n"); break;
			case map_header_t::ppFull:           fw.WriteS ("Preprocess=F\r\n"); break;
			default: assert (false);
		}

		// Save TreSize.
		fw.WriteS ("TreSize=");
		fw.WriteU (_map.Header.cTreSize);
		fw.WriteS ("\r\n");

		// Save TreMargin.
		fw.WriteS ("TreMargin=");
		fw.WriteF (_map.Header.fTreMargin);
		fw.WriteS ("\r\n");

		// Save RgnLimit.
		fw.WriteS ("RgnLimit=");
		fw.WriteU (_map.Header.cRgnLimit);
		fw.WriteS ("\r\n");

		// Save Transparent.
		if (_map.Header.bTransparent)
			fw.WriteS ("Transparent=Y\r\n");

		// Save POIIndex.
		fw.WriteS (_map.Header.bPOIIndex ? "POIIndex=Y\r\n" : "POIIndex=N\r\n");
		if (! _map.Header.bPOINumberFirst)
			fw.WriteS ("POINumberFirst=N\r\n");
		if (! _map.Header.bPOIZipFirst)
			fw.WriteS ("POIZipFirst=N\r\n");

		// Save the settings for commercial version of cgpsmapper.exe
		if (_map.Header.bMG)
			fw.WriteS ("MG=Y\r\n");
		if (_map.Header.bNumbering)
			fw.WriteS ("Numbering=Y\r\n");
		if (_map.Header.bRouting)
			fw.WriteS ("Routing=Y\r\n");
		if (_map.Header.bInvisible)
			fw.WriteS ("Invisible=Y\r\n");
		if (! _map.Header.strCopyright.empty ()) {
			fw.WriteS ("Copyright=");
			fw.WriteS (_map.Header.strCopyright.c_str ());
			fw.WriteS ("\r\n");
		}

		// Save custom parameters.
		for (map_header_t::extra_t::const_iterator ei = _map.Header.extra.begin (); ei != _map.Header.extra.end (); ++ ei) {
			fw.WriteS (ei->first);
			fw.WriteC ('=');
			fw.WriteS (ei->second);
			fw.WriteS ("\r\n");
		}

		// Save levels.
		if (! _map.Levels.empty ()) {
			fw.WriteS ("Levels=");
			_Write2Dec (fw, _map.Levels.size ());
			fw.WriteS ("\r\n");
		}

		size_t cLevel;
		for (cLevel = 0; cLevel < _map.Levels.size (); ++ cLevel) {
			const map_level_t & level = _map.Levels [cLevel];
			assert (level.btBits >= level.btBitsRangeTo);

			fw.WriteS ("Level");
			_Write2Dec (fw, cLevel);
			fw.WriteS ("=");
			fw.WriteU (level.btBits);
			fw.WriteS ("\r\n");

			if (level.btBits > level.btBitsRangeTo) {
				fw.WriteS ("LevelRange");
				_Write2Dec (fw, cLevel);
				fw.WriteS ("=");
				fw.WriteU (level.btBitsRangeTo);
				fw.WriteS ("\r\n");
			}
		}
		for (cLevel = 0; cLevel < _map.Levels.size (); ++ cLevel) {
			fw.WriteS ("Zoom");
			_Write2Dec (fw, cLevel);
			fw.WriteS ("=");
			fw.WriteU (_map.Levels [cLevel].cZoom);
			fw.WriteS ("\r\n");
		}
	}
	fw.WriteS ("[END-IMG ID]\r\n\r\n");
	map.cHeaderLen = fw.GetPos ();

	//
	// Save countries.
	//
	if (! _map.Countries.empty ()) {
		// Check if there are non-deleted countries.
		bool bHasCountries = false;
		for (countries_t::const_iterator i = map.Countries.begin (); i != map.Countries.end (); ++ i) {
			const country_t & country = * i;
			if (! country.IsDeleted ()) {
				bHasCountries = true;
				break;
			}
		}

		if (bHasCountries) {
			fw.WriteS ("[Countries]\r\n");

			size_t cIndex = 1;
			for (countries_t::iterator i = map.Countries.begin (); i != map.Countries.end (); ++ i) {
				country_t & country = * i;
				if (country.IsDeleted ())
					continue;

				country.cIndex = cIndex ++;

				fw.WriteS ("Country");
				fw.WriteU (country.cIndex);
				fw.WriteC ('=');
				fw.WriteS (country.strName);
				if (! country.strAbbrevName.empty ()) {
					fw.WriteS ("~[0x1d]");
					fw.WriteS (country.strAbbrevName);
				}
				fw.WriteS ("\r\n");
			}

			fw.WriteS ("[END-Countries]\r\n\r\n");
		}
	}

	//
	// Save regions.
	//
	if (! _map.Regions.empty ()) {
		// Check if there are non-deleted regions.
		bool bHasRegions = false;
		for (regions_t::const_iterator i = map.Regions.begin (); i != map.Regions.end (); ++ i) {
			const region_t & region = * i;
			if (! region.IsDeleted ()) {
				bHasRegions = true;
				break;
			}
		}

		if (bHasRegions) {
			fw.WriteS ("[Regions]\r\n");

			size_t cIndex = 1;
			for (regions_t::iterator i = map.Regions.begin (); i != map.Regions.end (); ++ i) {
				region_t & region = * i;
				if (region.IsDeleted ())
					continue;

				region.cIndex = cIndex ++;

				fw.WriteS ("Region");
				fw.WriteU (region.cIndex);
				fw.WriteC ('=');
				fw.WriteS (region.strName);
				if (! region.strAbbrevName.empty ()) {
					fw.WriteS ("~[0x1d]");
					fw.WriteS (region.strAbbrevName);
				}
				fw.WriteS ("\r\n");

				fw.WriteS ("CountryIdx");
				fw.WriteU (region.cIndex);
				fw.WriteC ('=');
				fw.WriteU (region.pCountry->cIndex);
				fw.WriteS ("\r\n");
			}
			
			fw.WriteS ("[END-Regions]\r\n\r\n");
		}
	}

	//
	// Save cities.
	//
	if (! _map.Cities.empty ()) {
		// Check if there are non-deleted cities.
		bool bHasCities = false;
		for (cities_t::const_iterator i = map.Cities.begin (); i != map.Cities.end (); ++ i) {
			const city_t & city = * i;
			if (! city.IsDeleted ()) {
				bHasCities = true;
				break;
			}
		}

		if (bHasCities) {
			fw.WriteS ("[Cities]\r\n");

			size_t cIndex = 1;
			for (cities_t::iterator i = map.Cities.begin (); i != map.Cities.end (); ++ i) {
				city_t & city = * i;
				if (city.IsDeleted ())
					continue;

				city.cIndex = cIndex ++;

				fw.WriteS ("City");
				fw.WriteU (city.cIndex);
				fw.WriteC ('=');
				fw.WriteS (city.strName);
				fw.WriteS ("\r\n");

				fw.WriteS ("RegionIdx");
				fw.WriteU (city.cIndex);
				fw.WriteC ('=');
				fw.WriteU (city.pRegion->cIndex);
				fw.WriteS ("\r\n");
			}
			
			fw.WriteS ("[END-Cities]\r\n\r\n");
		}
	}

	//
	// Save zip codes.
	//
	if (! _map.Zips.empty ()) {
		fw.WriteS ("[ZipCodes]\r\n");

		// TODO: check if there are non-deleted zip codes.

		size_t cIndex = 1;
		for (zips_t::iterator i = map.Zips.begin (); i != map.Zips.end (); ++ i) {
			zip_t & zip = * i;
			if (zip.IsDeleted ())
				continue;

			zip.cIndex = cIndex ++;

			fw.WriteS ("ZipCode");
			fw.WriteU (zip.cIndex);
			fw.WriteC ('=');
			fw.WriteS (zip.strName);
			fw.WriteS ("\r\n");
		}
			
		fw.WriteS ("[END-ZipCodes]\r\n\r\n");
	}

	//
	// Save highways.
	//
	if (! _map.Highways.empty ()) {
		fw.WriteS ("[Highways]\r\n");

		size_t cIndex = 1;
		for (highways_t::iterator i = map.Highways.begin ();i != map.Highways.end (); ++ i) {
			highway_t & highway = * i;
			if (highway.IsDeleted ())
				continue;

			highway.cIndex = cIndex ++;

			fw.WriteS ("Highway");
			fw.WriteU (highway.cIndex);
			fw.WriteC ('=');
			fw.WriteS (highway.strName);
			fw.WriteS ("\r\n");

			fw.WriteS ("RegionIdx");
			fw.WriteU (highway.cIndex);
			fw.WriteC ('=');
			fw.WriteU (highway.pRegion->cIndex);
			fw.WriteS ("\r\n");
		}
			
		fw.WriteS ("[END-Highways]\r\n\r\n");
	}

	//
	// Save turn restrictions.
	//
	if (! map.rgrestrictions.empty ()) {
		// Sort turn restrictions by NodeID.
		typedef std::multimap<DWORD, const rgrestriction_t *> id2r_t;
		id2r_t id2r;
		for (rgrestrictions_t::const_iterator r = map.rgrestrictions.begin (); r != map.rgrestrictions.end (); ++ r) {
			assert (r->first != NULL);
			if (r->first != NULL)
				id2r.insert (id2r_t::value_type (r->first->dwNodeID, & r->second));
		}

		for (id2r_t::const_iterator ir = id2r.begin (); ir != id2r.end (); ++ ir) {
			const rgrestriction_t & restriction = * ir->second;
			if (restriction.IsUsed ()) {
				const rgnode_t * const pPrevNode = restriction.pFromRoad->GetPrevNode (restriction.pNode, restriction.bFromDirection != 0);
				const rgnode_t * const pNextNode = restriction.pToRoad  ->GetNextNode (restriction.pNode, restriction.bToDirection   != 0);
				assert (pPrevNode != NULL && pNextNode != NULL);
				if (pPrevNode == NULL || pNextNode == NULL)
					continue;

				fw.WriteS ("[Restrict]\r\n");

				fw.WriteS ("Nod=");
				fw.WriteU (restriction.pNode->dwNodeID);
				fw.WriteS ("\r\n");

				fw.WriteS ("TraffPoints=");
				fw.WriteU (pPrevNode->dwNodeID);
				fw.WriteC (',');
				fw.WriteU (restriction.pNode->dwNodeID);
				fw.WriteC (',');
				fw.WriteU (pNextNode->dwNodeID);
				fw.WriteS ("\r\n");

				fw.WriteS ("TraffRoads=");
				fw.WriteU (restriction.pFromRoad->pExtra->dwRoadID);
				fw.WriteC (',');
				fw.WriteU (restriction.pToRoad  ->pExtra->dwRoadID);
				fw.WriteS ("\r\n");

				fw.WriteS ("Time=");
				fw.WriteS (restriction.strTime);
				fw.WriteS ("\r\n");

				fw.WriteS ("[END-Restrict]\r\n\r\n");
			}
		}
	}

	//
	// Save all unknown sections.
	// NOTE: some of them may be required to be placed before objects.
	//
	for (sections_t::const_iterator iSection = _map.ExtraSections.begin (); iSection != _map.ExtraSections.end (); ++ iSection)
		fw.WriteS (iSection->strText.c_str ());

	fw.WriteS ("\r\n");

	//
	// Save objects.
	//
	size_t cRgn = 0;
	const size_t cRgns = _map.GetRgnCount ();
	for (rgn_t * pRgn = map.pFirstRgn; pRgn; pRgn = pRgn->pNextRgn, ++ cRgn) {
		rgn_t & rgn = * pRgn;
		if (rgn.IsDeleted () || rgn.IsWTR ())
			continue;

		// Update the fields depending on file schema.
		rgn.dwFlags  &= ~rgn_t::fModified;
		rgn.cFileLine = fw.GetLine ();
		rgn.cBeginPos = fw.GetPos ();
//		rgn.cOrdinal  = cOrdinal ++;
		rgn.pFileRef  = & file_ref;

		// Save comment.
		if (! rgn.strComment.empty ()) {
			fw.WriteC (';');
			for (size_t c = 0; c < rgn.strComment.size (); ++ c) {
				const char ch = rgn.strComment [c];
				fw.WriteC (ch);
				if (ch == '\n' && c < rgn.strComment.size () - 1)
					fw.WriteC (';');
			}
			if (rgn.strComment [rgn.strComment.size () - 1] != '\n')
				fw.WriteS ("\r\n");
		}

		// Save kind & type.
		if (g_bPolish074) {
			if (rgn.Kind == rkPoint)
				fw.WriteS ("[POI]\r\nType=0x");
			else if (rgn.Kind == rkPolyline)
				fw.WriteS ("[POLYLINE]\r\nType=0x");
			else {
				assert (rgn.Kind == rkPolygon);
				fw.WriteS ("[POLYGON]\r\nType=0x");
			}
			fw.WriteX (rgn.ushType);
			fw.WriteS ("\r\n");
		} else {
			USHORT ushType = rgn.ushType;
			if (rgn.Kind == rkPoint) {
				if (rgn.dwFlags & rgn_t::fIndexedCity) {
					ushType >>= 8;
					fw.WriteS ("[RGN20]\r\nType=0x");
				} else
					fw.WriteS ("[RGN10]\r\nType=0x");
			} else if (rgn.Kind == rkPolyline)
				fw.WriteS ("[RGN40]\r\nType=0x");
			else {
				assert (rgn.Kind == rkPolygon);
				fw.WriteS ("[RGN80]\r\nType=0x");
			}
			fw.WriteX (ushType);
			fw.WriteS ("\r\n");
		}

		// Save Background.
		if (_map.pTypeSet->pfIsBackground (rgn))
			fw.WriteS ("Background=Y\r\n");

		// Save Label, if any.
		if (! rgn.strLabel.empty ()) {
			fw.WriteS ("Label=");
			fw.WriteS (rgn.strLabel.c_str ());
			fw.WriteS ("\r\n");
		}

		// Save levels, if any.
		if (rgn.uchEndLevel != 0) {
			fw.WriteS (g_bPolish074 ? "EndLevel=" : "Levels=");
			fw.WriteU (rgn.uchEndLevel);
			fw.WriteS ("\r\n");
		}

		// Save direction, if any.
		if (rgn.dwFlags & rgn_t::fDirIndicator)
			fw.WriteS ("DirIndicator=1\r\n");
		// Save indexed city, if any.
		if (rgn.dwFlags & rgn_t::fIndexedCity)
			fw.WriteS ("City=Y\r\n");
		// Save 'don't index', if any.
		if (rgn.dwFlags & rgn_t::fDontFind)
			fw.WriteS ("DontFind=Y\r\n");

		if (rgn.pExtra) {
			// Save house number, if any.
			if (! rgn.pExtra->strNumber.empty ()) {
				fw.WriteS ("HouseNumber=");
				fw.WriteS (rgn.pExtra->strNumber.c_str ());
				fw.WriteS ("\r\n");
			}

			// Save street description, if any.
			if (! rgn.pExtra->strStreetDesc.empty ()) {
				fw.WriteS ("StreetDesc=");
				fw.WriteS (rgn.pExtra->strStreetDesc.c_str ());
				fw.WriteS ("\r\n");
			}

			// Save city index, if any.
			if (rgn.pExtra->pCity) {
				fw.WriteS ("CityIdx=");
				fw.WriteU (rgn.pExtra->pCity->cIndex);
				fw.WriteS ("\r\n");
			}

			// Save zip code, if any.
			if (rgn.pExtra->pZip) {
				fw.WriteS ("ZipIdx=");
				fw.WriteU (rgn.pExtra->pZip->cIndex);
				fw.WriteS ("\r\n");
			}

			// Save phone number, if any.
			if (! rgn.pExtra->strPhone.empty ()) {
				fw.WriteS ("Phone=");
				fw.WriteS (rgn.pExtra->strPhone.c_str ());
				fw.WriteS ("\r\n");
			}

			// Save fax number, if any.
			if (! rgn.pExtra->strFax.empty ()) {
				fw.WriteS ("Fax=");
				fw.WriteS (rgn.pExtra->strFax.c_str ());
				fw.WriteS ("\r\n");
			}

			// Save email, if any.
			if (! rgn.pExtra->strEmail.empty ()) {
				fw.WriteS ("Email=");
				fw.WriteS (rgn.pExtra->strEmail.c_str ());
				fw.WriteS ("\r\n");
			}

			// Save URL, if any.
			if (! rgn.pExtra->strWebPage.empty ()) {
				fw.WriteS ("WebPage=");
				fw.WriteS (rgn.pExtra->strWebPage.c_str ());
				fw.WriteS ("\r\n");
			}

			// Save description, if any.
			if (! rgn.pExtra->strDescription.empty ()) {
				const string_t & strDescription = rgn.pExtra->strDescription;
				const size_t cLen = strDescription.size ();
				size_t cLineStart = 0;
				for (size_t c = 0; c < cLen; ++ c) {
					const char ch = strDescription [c];
					if (ch == '\n' || ch == '\r') {
						if (c > cLineStart) {
							// Output each line in description as separate "Text=".
							fw.WriteS ("Text=");
							fw.WriteS (strDescription.c_str () + cLineStart, c - cLineStart);
							fw.WriteS ("\r\n");
						} else if (c + 1 < cLen) {
							const char chNext = strDescription [c + 1];
							if (ch == chNext || (ch == '\n' && chNext == '\r'))
								// Output empty line.
								fw.WriteS ("Text=\r\n");
						}
						cLineStart = c + 1;
					}
				}
				if (cLen > cLineStart) {
					// Output the last line in description.
					fw.WriteS ("Text=");
					fw.WriteS (strDescription.c_str () + cLineStart, cLen - cLineStart);
					fw.WriteS ("\r\n");
				}
			}

			// Save highway index, if any.
			if (rgn.pExtra->pHighway) {
				fw.WriteS ("HighwayIdx=");
				fw.WriteU (rgn.pExtra->pHighway->cIndex);
				fw.WriteS ("\r\n");
			}

			// TODO: save exit facilities etc.
		}

		if (rgn.pExtra) {
			// Entry point
			if (rgn.pExtra->IsEntryPointDefined ()) {
				fw.WriteS ("EntryPoint=(");
				fw.WriteF (rgn.pExtra->ptEntryPoint.y);
				fw.WriteS (",");
				fw.WriteF (rgn.pExtra->ptEntryPoint.x);
				fw.WriteS (")\r\n");
			}
			// RoadID.
			if (rgn.pExtra->dwRoadID != -1) {
				fw.WriteS ("RoadID=");
				fw.WriteU (rgn.pExtra->dwRoadID);
				fw.WriteS ("\r\n");
			}
			//ManeuverType
			if (rgn.pExtra->dwManeuverType != 0) {
				fw.WriteS ("ManeuverType=");
				fw.WriteU (rgn.pExtra->dwManeuverType);
				fw.WriteS ("\r\n");
			}
			// Route parameters.
			if (rgn.pExtra->dwRouteParams != 0) {
				fw.WriteS ("RouteParam=");
				fw.WriteU (rgn.pExtra->fSpeedLimit);
				fw.WriteC (',');
				fw.WriteU (rgn.pExtra->fRouteClass);
				fw.WriteC (',');
				fw.WriteU (rgn.pExtra->bOneWay);
				fw.WriteC (',');
				fw.WriteU (rgn.pExtra->bToll);
				fw.WriteC (',');
				fw.WriteU (rgn.pExtra->bNoEmergency);
				fw.WriteC (',');
				fw.WriteU (rgn.pExtra->bNoDelivery);
				fw.WriteC (',');
				fw.WriteU (rgn.pExtra->bNoCar);
				fw.WriteC (',');
				fw.WriteU (rgn.pExtra->bNoBus);
				fw.WriteC (',');
				fw.WriteU (rgn.pExtra->bNoTaxi);
				fw.WriteC (',');
				fw.WriteU (rgn.pExtra->bNoPedestrian);
				fw.WriteC (',');
				fw.WriteU (rgn.pExtra->bNoBicycle);
				fw.WriteC (',');
				fw.WriteU (rgn.pExtra->bNoTruck);
				fw.WriteS ("\r\n");

				if (rgn.pExtra->bNoThrough) {
					fw.WriteS ("RouteParamExt=");
					fw.WriteU (rgn.pExtra->bNoThrough);
					fw.WriteS ("\r\n");
				}

				if (rgn.pExtra->bNoJoin)
					fw.WriteS ("DontJoin=Y\r\n");
			}
		}

		// Save elements.
		const size_t cElements = rgn.elements.size ();
		const rgn_element_t * const pElement0 = rgn.GetNonDeletedElement (0, 0);
		size_t cSavedElementIdx = 0;
		for (size_t cElement = 0; cElement < cElements; ++ cElement) {
			rgn_element_t & element = rgn.elements [cElement];
			if (element.bDeleted)
				continue;

			const points_t & points = element.points;
			const size_t cPoints = points.size ();
			if (cPoints == 0)
				continue;

			element.cFileOffset = fw.GetPos ();

			fw.WriteS ("Data");
			_Write2Dec (fw, element.cLevel);
			fw.WriteS ("=(");

			for (size_t cPoint = 0; cPoint < cPoints; ++ cPoint) {
				const point_t & point = points [cPoint];

				if (cPoint != 0)
					fw.WriteS ("),(");
				fw.WriteF (point.y);
				fw.WriteS (",");
				fw.WriteF (point.x);
			}

			fw.WriteS (")\r\n");

			if (element.pExtra) {
				const point_extras_t & PointExtras = element.pExtra->PointExtras;
				const size_t cPoints = PointExtras.size ();

				// RG nodes.
				if (HasRGNodes (PointExtras)) {
					if (& element == pElement0) {
						size_t cNode = 0;
						for (size_t cPoint = 0; cPoint < cPoints; ++ cPoint) {
							const rgnode_t * const pRGNode = PointExtras [cPoint].GetRGNode ();
							if (pRGNode == NULL)
								continue;

							fw.WriteS ("Nod");
							fw.WriteU (cNode + 1);
							fw.WriteC ('=');
							fw.WriteU (cPoint);
							fw.WriteC (',');
							fw.WriteU (pRGNode != NULL ? pRGNode->dwNodeID : 0);
							fw.WriteC (',');
							fw.WriteU (pRGNode != NULL ? (pRGNode->bIsExternal ? 1 : 0) : 0);
							fw.WriteS ("\r\n");
							++ cNode;
						}
					} else {
						fw.WriteS ("Nodes");
						fw.WriteU (cSavedElementIdx);
						fw.WriteC ('=');
						bool bFirst = true;
						for (size_t cPoint = 0; cPoint < cPoints; ++ cPoint) {
							const rgnode_t * const pRGNode = PointExtras [cPoint].GetRGNode ();
							if (pRGNode == NULL)
								continue;

							if (bFirst)
								bFirst = false;
							else
								fw.WriteC (',');

							fw.WriteC ('(');
							fw.WriteU (cPoint);
							fw.WriteC (',');
							fw.WriteU (pRGNode->dwNodeID);
							if (pRGNode->bIsExternal)
								fw.WriteS (",1");
							fw.WriteC (')');
						}
						fw.WriteS ("\r\n");
					}
				}

				// Numbers.
				if (HasNumbers (PointExtras)) {
					if (& element == pElement0) {
						const numbers_t * pNumbersPrev = NULL;
						size_t cPointPrev = 0;
						size_t cNode = 0;
						for (size_t cPoint = 0; cPoint < cPoints; ++ cPoint) {
							const numbers_t & Numbers = PointExtras [cPoint].Numbers;
							if (! Numbers.bHasNumbers && cPoint + 1 < cPoints)
								continue;

							if (pNumbersPrev) {
								fw.WriteS ("Numbers");
								fw.WriteU (cNode + 1);
								fw.WriteC ('=');
								_WriteHouseNumbers (fw, rgn, cPointPrev, * pNumbersPrev, Numbers);
								fw.WriteS ("\r\n");

								++ cNode;
							}

							pNumbersPrev = & Numbers;
							cPointPrev = cPoint;
						}
					} else {
						fw.WriteS ("NumbersExt");
						fw.WriteU (cSavedElementIdx);
						fw.WriteC ('=');

						const numbers_t * pNumbersPrev = NULL;
						size_t cPointPrev = 0;
						bool bFirst = true;
						for (size_t cPoint = 0; cPoint < cPoints; ++ cPoint) {
							const numbers_t & Numbers = PointExtras [cPoint].Numbers;
							if (! Numbers.bHasNumbers && cPoint + 1 < cPoints)
								continue;

							if (pNumbersPrev) {
								if (bFirst)
									bFirst = false;
								else
									fw.WriteC (',');

								fw.WriteC ('(');
								_WriteHouseNumbers (fw, rgn, cPointPrev, * pNumbersPrev, Numbers);
								fw.WriteC (')');
							}

							pNumbersPrev = & Numbers;
							cPointPrev = cPoint;
						}
						fw.WriteS ("\r\n");
					}
				}
			}

			++ cSavedElementIdx;
		}

		// Save origins.
		for (size_t cOrigin = 0; cOrigin < rgn.origins.size (); ++ cOrigin) {
			rgn_origin_t & origin = rgn.origins [cOrigin];
			if (origin.bDeleted)
				continue;

			origin.cFileOffset = fw.GetPos ();

			fw.WriteS (g_bPolish074 ? "Data" : "Origin");
			_Write2Dec (fw, origin.cLevel);
			fw.WriteS ("=(");
			fw.WriteF (origin.point.y);
			fw.WriteS (",");
			fw.WriteF (origin.point.x);
			fw.WriteS (")\r\n");
		}

		// Save extras.
		if (rgn.pExtra && ! rgn.pExtra->strExtra.empty ()) {
			const string_t & strExtra = rgn.pExtra->strExtra;
			fw.WriteS (strExtra.c_str ());

			const size_t cLen = strExtra.size ();
			if (
				cLen < 2 ||
				strExtra [cLen - 2] != '\r' ||
				strExtra [cLen - 1] != '\n'
			)
				fw.WriteS ("\r\n");
		}

		if (g_bPolish074)
			fw.WriteS ("[END]\r\n\r\n");
		else {
			if (rgn.Kind == rkPoint) {
				if (rgn.dwFlags & rgn_t::fIndexedCity) {
					fw.WriteS ("[END-RGN20]\r\n\r\n");
				} else
					fw.WriteS ("[END-RGN10]\r\n\r\n");
			} else if (rgn.Kind == rkPolyline)
				fw.WriteS ("[END-RGN40]\r\n\r\n");
			else {
				assert (rgn.Kind == rkPolygon);
				fw.WriteS ("[END-RGN80]\r\n\r\n");
			}
		}

		rgn.cEndPos = fw.GetPos ();

		SetProgressStatus (_cProgressFrom + fast_floor (float (_cProgressRange)*cRgn/cRgns + .5f));
	}

	//
	// Save references to the images.
	//
	for (renderers_t::const_iterator ir = _map.renderers.begin (); ir != _map.renderers.end (); ++ ir) {
		const renderer_base_t * const pRenderer = * ir;
		if (pRenderer->bOverlay || pRenderer->bDeleted)
			continue;

		fw.WriteFmt (";@IMAGE,\"%s\",%.6f,%.6f,%d\r\n", pRenderer->strFileName.data (), pRenderer->ptCorrectionShift.x, pRenderer->ptCorrectionShift.y, pRenderer->bVisible ? 1 : 0);
	}

	//
	// Save references to the attached waypoints and tracks.
	//
	for (file_refs_t::const_iterator iFileRef = _map.file_refs.begin (); iFileRef != _map.file_refs.end (); ++ iFileRef) {
		if (iFileRef->bDeleted || ! iFileRef->bAttachment)
			continue;

		fw.WriteFmt (";@FILE,\"%s\",%d\r\n", iFileRef->strFileName.data (), iFileRef->bVisible ? 1 : 0);
	}

	fw.FlushBuffer ();

	SetProgressStatus (_cProgressFrom + _cProgressRange);

	if (fw.GetOverflow ())
		ErrorMsgBox ("File has been written partialy.\nThis may be caused by disk overflow.");
}
